#include <iostream>
#include "heap.h"
#include <cstdlib>
#include <ctime>
#include <vector>
#include <utility>
#include <sstream>
#include <iomanip>

#define PRINT(X) cout << #X << " = " << X << "\n";


using namespace std;

int numOfOpsInStage2, numOfExecutions,stage2NumOfThreads, stage1SizeofHeap;
float stage2inserts, stage2deletes, stage2getMins;
Heap h;

/**
 * Execute the stage I of the tests: create a heap of size 1 and inserts the specified number of elements 
 * @param size the number of elements to insert
 * @return the total time to do the inserts
 */

double stage1(int size)
{	
	h = Heap_Create(1);

	clock_t start = clock();
	
	for(int i = 0; i < size; i++)
		Heap_Insert(h, rand(), rand());
	
	clock_t total = clock() - start;
	
	return static_cast<double>(total)/CLOCKS_PER_SEC;	
}

/**
 * Runs the threads with the probality of the running test for insertion, deletion and GetMin.
 * Runs a different "stopwatch" for each sort of operation
 * @return a vector with the total execution time for each sort of operation for this thread
 */
void* stage2Thread(void*)
{
	int opsToDo = numOfOpsInStage2 / stage2NumOfThreads;
	
	unsigned int seed = clock();
	
	clock_t clock_insert = 0, clock_delete = 0, clock_get_min = 0;
	int numOfInsert = 0, numOfDelete = 0, numOfGetMin = 0;
	
	for(int i = 0; i < opsToDo; i++)
	{
		double flip = static_cast<double>(rand_r(&seed)) / RAND_MAX;
		if(flip < stage2inserts)
		{	
			numOfInsert ++;
			clock_t start = clock();
			
			Heap_Insert(h, rand_r(&seed), rand_r(&seed));
						
			clock_insert += (clock() - start);
		}
		else if(flip - stage2inserts < stage2deletes)
		{
			numOfDelete ++;
			clock_t start = clock();
			
			Heap_Pop(h, NULL, NULL);
			
			clock_delete += (clock() - start);
		}
		else if(flip - stage2inserts - stage2deletes < stage2getMins)
		{
			numOfGetMin ++;
			clock_t start = clock();
			
			Heap_GetMin(h, NULL, NULL);
			
			clock_get_min += (clock() - start);
		}	
	}
		
	vector<pair<clock_t, int> > *result = new vector<pair<clock_t, int> >(3);
	(*result)[0] = make_pair(clock_insert, numOfInsert);
	(*result)[1] = make_pair(clock_delete, numOfDelete);
	(*result)[2] = make_pair(clock_get_min, numOfGetMin);
	
	return result;
}

/**
 * Executes the stage II of the tests: creates and runs the threads, and finally
 * updates the average and total times for each sort of operation; then, delete the heap
 * @param averageTimeInsert Average Execution Time of an insert
 * @param averageTimeDelete Average Execution Time of insert
 * @param averageTimeGetMin Average Execution Time of insert
 * @param totalTimeStage2 Total Execution Time of stage II
 */
// 
void stage2(double &averageTimeInsert, double &averageTimeDelete, double &averageTimeGetMin, double &totalTimeStage2)
{	
	struct timespec start, finish;
	double elapsed;

	clock_gettime(CLOCK_MONOTONIC, &start);

	vector<pthread_t> threads(stage2NumOfThreads);
	
	for(int i = 0; i < stage2NumOfThreads; i++)
	{
	   pthread_create(&threads[i], NULL, stage2Thread, NULL);
	}
	
	vector<vector<pair<clock_t, int> > *> results(stage2NumOfThreads);
	
	
	clock_t clock_insert = 0, clock_delete = 0, clock_get_min = 0;
	int numOfInsert = 0, numOfDelete = 0, numOfGetMin = 0;
	for(int i = 0; i < stage2NumOfThreads; i++)
	{
		pthread_join(threads[i], (void**)&results[i] );
		clock_insert += (*results[i])[0].first;
		numOfInsert += (*results[i])[0].second;
		clock_delete += (*results[i])[1].first;
		numOfDelete += (*results[i])[1].second;
		clock_get_min += (*results[i])[2].first;
		numOfGetMin += (*results[i])[2].second;
	}
	
	clock_gettime(CLOCK_MONOTONIC, &finish);

	totalTimeStage2 = (finish.tv_sec - start.tv_sec) + (finish.tv_nsec - start.tv_nsec) / 1000000000.0;;
	averageTimeInsert = (static_cast<double>(clock_insert)/CLOCKS_PER_SEC)/numOfInsert;
	averageTimeDelete = (static_cast<double>(clock_delete)/CLOCKS_PER_SEC)/numOfDelete;
	if(numOfGetMin != 0)
		averageTimeGetMin = (static_cast<double>(clock_get_min)/CLOCKS_PER_SEC)/numOfGetMin;
	
	
	
	Heap_Delete(h);
}

/**
 * Runs the requested test "numOfExecutions" times and display the results for each execution and then the average results
 */
int runTest()
{	
	double totalTimeStage1=0, averageTimeInsert=0, averageTimeDelete=0, averageTimeGetMin=0, totalTimeStage2=0;
	double totalStage1=0, totalStage2=0, totalTimeInsert=0, totalTimeDelete=0, totalTimeGetMin=0;
	
	cout << "Running test with the following parameters:" << endl;
	PRINT(stage1SizeofHeap);
	PRINT(numOfOpsInStage2 );
	PRINT(stage2NumOfThreads) ;
	PRINT(stage2inserts );
	PRINT(stage2deletes );
	PRINT(stage2getMins);
	PRINT(numOfExecutions);
	
	cout << setw(10) << "Stage1" << "\t|  " << setw(10) << "Stage2"<< "\t|  " << setw(10) << "Total Time "<< "\t|  "<< setw(25) << "Average Insertion Time" << "\t|  " << setw(25) << "Average Deletion Time";
	if (stage2getMins!=0)
		cout << "\t|  " << setw(25) << "Average GetMin Time";
	cout << endl;
	
	for(int i =0; i < 100; i++) cout << '-';
	cout << endl;
	
	
	for (int i=0; i<numOfExecutions; i++)
	{
		totalTimeStage1 = stage1(stage1SizeofHeap);
		stage2(averageTimeInsert, averageTimeDelete, averageTimeGetMin, totalTimeStage2);
		totalStage1+=totalTimeStage1;
		totalStage2+=totalTimeStage2;
		totalTimeInsert+=averageTimeInsert;
		totalTimeDelete+=averageTimeDelete;
		
		cout << setw(10) << totalTimeStage1 << "\t|  " << setw(10) << totalTimeStage2 << "\t|  " << setw(10) << totalTimeStage1 + totalTimeStage2 << "\t|  "<< setw(25) << averageTimeInsert << "\t|  " << setw(25) << averageTimeDelete;
		if (stage2getMins!=0)
		{
			totalTimeGetMin+=averageTimeGetMin;
			cout << "\t|  " << setw(25) << averageTimeGetMin;
		}
		cout << endl;
		
	}
	
	cout << "Average: " << endl;
	cout <<  setw(10) << totalStage1/numOfExecutions << "\t|  " << setw(10) << totalStage2/numOfExecutions << "\t|  " << setw(10) << (totalStage1+totalStage2)/numOfExecutions << "\t|  "<< setw(25) << totalTimeInsert/numOfExecutions << "\t|  " <<  setw(25) << totalTimeDelete/numOfExecutions;
	
	if (stage2getMins!=0)
		cout << "\t|  " << setw(25) << totalTimeGetMin/numOfExecutions;
	cout << endl << endl;;
	
	
	
}
int main(int argc, char ** argv)
{
	if(argc < 8)
	{
		cout << "not enough parameters" << endl;
		cout << "usage: ./benchmark <stage1SizeofHeap> <numOfOpsInStage2> <stage2NumOfThreads> <stage2inserts> <stage2deletes> <stage2getMins> <numOfExecutions>" << endl;
		return 0;
	}
	
	istringstream ( argv[1] ) >> stage1SizeofHeap;
	istringstream ( argv[2] ) >> numOfOpsInStage2 ;
	istringstream ( argv[3] ) >> stage2NumOfThreads ;
	istringstream ( argv[4] ) >> stage2inserts ;
	istringstream ( argv[5] ) >> stage2deletes ;
	istringstream ( argv[6] ) >> stage2getMins;
	istringstream ( argv[7] ) >> numOfExecutions;

	srand(time(0));

	runTest();
	return 0;
}


